iT邦幫忙

2026 iThome 鐵人賽

DAY 28
0
Kubernetes

從零開始的 Kubernetes 基礎觀念與實作系列 第 28 篇

# 從零開始的 Kubernetes 基礎觀念與實作 DAY28

  • 分享至 

  • xImage
  •  

Kubernetes 綜合實作(二):資料持久化與 Ingress

上一篇透過 Deployment、Service、ConfigMap 和 Secret,建立了一個簡單的 Web API,並成功將應用程式部署到 Kubernetes。

不過,目前的應用程式還有兩個限制:

  • 應用程式沒有資料持久化功能。
  • Service 使用 ClusterIP,主要提供 Cluster 內部存取。

這篇將延續 DAY27 的專案,加入資料讀寫功能,並使用 PVC 保存資料。最後透過 Ingress,讓使用者可以使用 HTTP 路徑存取應用程式。

修改 Web API

首先修改 DAY27 建立的 app.py,加入簡單的資料讀寫功能。

為了方便實作,這裡使用 JSON 檔案保存資料,不額外安裝資料庫。

from flask import Flask, jsonify, request
import os
import json

app = Flask(__name__)

DATA_DIR = "/data"
DATA_FILE = os.path.join(DATA_DIR, "messages.json")

os.makedirs(DATA_DIR, exist_ok=True)

def load_messages():
    if not os.path.exists(DATA_FILE):
        return []

    with open(DATA_FILE, "r", encoding="utf-8") as f:
        return json.load(f)

def save_messages(messages):
    with open(DATA_FILE, "w", encoding="utf-8") as f:
        json.dump(messages, f, ensure_ascii=False)

@app.route("/")
def index():
    return jsonify({
        "message": os.getenv("APP_MESSAGE", "Hello Kubernetes"),
        "version": "2.0"
    })

@app.route("/config")
def config():
    return jsonify({
        "app_name": os.getenv("APP_NAME", "demo"),
        "secret_configured": bool(os.getenv("APP_SECRET"))
    })

@app.route("/messages", methods=["GET"])
def get_messages():
    return jsonify(load_messages())

@app.route("/messages", methods=["POST"])
def add_message():
    data = request.get_json(silent=True)

    if not isinstance(data, dict):
        return jsonify({"error": "Invalid JSON"}), 400

    message = data.get("message")

    if not isinstance(message, str) or not message.strip():
        return jsonify({"error": "Invalid message"}), 400

    messages = load_messages()
    messages.append(message)
    save_messages(messages)

    return jsonify({
        "status": "success",
        "message": message
    }), 201

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

這次新增了兩個 API:

Method Path 功能
GET /messages 讀取已保存的訊息
POST /messages 新增訊息

資料會儲存在 /data/messages.json。

不過,容器本身的檔案系統並不適合用來保存需要長期保留的資料。當 Pod 被重新建立時,原本寫在容器可寫層中的資料通常不會保留。

因此,接下來使用 PVC 提供持久化儲存空間。

這個 JSON 範例只用來示範持久化儲存,不適合多個程序同時寫入,也沒有完整的資料庫交易保護。正式應用程式應考慮使用資料庫。

建立 PersistentVolumeClaim

DAY11 已經介紹過 PV 與 PVC 的基本概念。

這次透過 PVC 向 Kubernetes 申請儲存空間,再將它掛載到容器的 /data 目錄。

建立 k8s/pvc.yaml:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: final-demo-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

這裡設定:

  • ReadWriteOnce:允許 Volume 以讀寫模式掛載到單一 Node。
  • storage: 1Gi:申請 1Gi 的儲存空間。

這份 YAML 沒有指定 storageClassName,因此會使用 Cluster 的預設 StorageClass。

可以先查看:

kubectl get storageclass

確認是否存在預設 StorageClass。

ReadWriteOnce 並不代表只能有一個 Pod 使用 Volume,而是限制 Volume 的讀寫掛載節點。為了避免這次簡單的 JSON 檔案發生並行寫入問題,我們會將 Deployment 調整成一個副本。

修改 Deployment

接著修改 k8s/deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: final-demo
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: final-demo
  template:
    metadata:
      labels:
        app: final-demo
    spec:
      containers:
        - name: web
          image: k8s-final-demo:2.0
          imagePullPolicy: Never
          ports:
            - containerPort: 5000
          envFrom:
            - configMapRef:
                name: final-demo-config
            - secretRef:
                name: final-demo-secret
          volumeMounts:
            - name: app-data
              mountPath: /data
      volumes:
        - name: app-data
          persistentVolumeClaim:
            claimName: final-demo-pvc

與上一篇相比,這次增加了:

  • replicas: 1:使用單一 Pod,避免 JSON 檔案被多個副本同時寫入。
  • strategy.type: Recreate:更新時先停止舊 Pod,再建立新 Pod。
  • volumeMounts:將儲存空間掛載到 /data。
  • volumes:指定使用 final-demo-pvc。

透過這樣的設定,容器即使被重新建立,只要 PVC 與底層儲存資料仍然存在,就能繼續讀取原本保存的資料。

重新建立映像檔

由於修改了 app.py,需要重新建立 Docker 映像檔。

在專案根目錄執行:

docker build -t k8s-final-demo:2.0 .

接著載入 Minikube:

minikube image load k8s-final-demo:2.0

如果使用多節點 Minikube,需要確保 Pod 被排程到的 Node 能取得這個映像檔。

部署 PVC 與 Deployment

先建立 PVC:

kubectl apply -f k8s/pvc.yaml

接著更新 Deployment:

kubectl apply -f k8s/deployment.yaml

查看 PVC:

kubectl get pvc

預期 PVC 會進入 Bound 狀態,代表已成功綁定儲存空間。
image

部分 StorageClass 使用 WaitForFirstConsumer,可能需要等到 Pod 排程後才完成綁定。

接著查看 Pod:

kubectl get pods

確認新的 Pod 正常啟動。
image

測試資料持久化

首先使用 port-forward 存取 Service:

kubectl port-forward svc/final-demo-service 8080:80

接著開啟另一個 PowerShell 視窗,新增一筆資料:

Invoke-RestMethod `
  -Uri http://localhost:8080/messages `
  -Method POST `
  -ContentType "application/json" `
  -Body '{"message":"Hello PVC"}'

會回傳:
image

接著讀取資料:

Invoke-RestMethod http://localhost:8080/messages

會看到:
image

代表應用程式已成功將資料寫入掛載的儲存空間。

刪除 Pod 測試

接下來手動刪除目前的 Pod,觀察 Kubernetes 是否能重新建立 Pod,並保留資料。

先查看 Pod:

kubectl get pods

取得 Pod 名稱後執行:

kubectl delete pod <pod-name>

由於 Deployment 設定了 replicas: 1,Kubernetes 會自動建立新的 Pod,維持指定的副本數量。

等待新的 Pod 啟動:

kubectl get pods -w

新的 Pod 啟動後,由於原本的 kubectl port-forward 連線可能隨著舊 Pod 被刪除而中斷,因此需要重新建立連線:

kubectl port-forward svc/final-demo-service 8080:80

接著在另一個 PowerShell 視窗再次讀取資料:

Invoke-RestMethod http://localhost:8080/messages

如果仍然看到:

[
  "Hello PVC"
]

就代表資料並沒有隨著原本的 Pod 一起消失。

這是因為資料儲存在 PVC 所提供的持久化儲存空間,而不是只存在原本容器的可寫層中。

注意:這裡測試的是 Pod 重建後的資料保留,不代表資料已經具備跨 Node、跨 Cluster 的備援能力。實際資料可用性仍取決於底層 StorageClass 和儲存系統。

使用 Ingress 存取應用程式

前面已經使用 Service 提供 Cluster 內部存取。

接下來透過 Ingress,讓 HTTP 請求可以依照設定的路徑轉送到 Service。

首先確認 Minikube 已啟用 Ingress Controller:

minikube addons enable ingress

查看 Ingress Controller 是否正常執行:

kubectl get pods -n ingress-nginx

接著建立 k8s/ingress.yaml:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: final-demo-ingress
spec:
  ingressClassName: nginx
  rules:
    - host: final-demo.test
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: final-demo-service
                port:
                  number: 80

這份 Ingress 會將符合 final-demo.test Host 的 HTTP 請求轉送到 final-demo-service。

建立 Ingress:

kubectl apply -f k8s/ingress.yaml

查看狀態:

kubectl get ingress

image

測試 Ingress

建立 Ingress 後,接下來測試 HTTP 請求是否能夠透過 Ingress Controller 轉送到對應的 Service。

由於不同的 Minikube Driver 可能有不同的網路設定,在 Windows 使用 Docker Driver 時,可能無法直接透過 Minikube 的 Node IP 存取 Ingress Controller。

因此,這裡使用 kubectl port-forward 將 Ingress Controller 的 HTTP Port 轉送到本機進行測試。

首先開啟一個 PowerShell 視窗,執行:

kubectl port-forward -n ingress-nginx svc/ingress-nginx-controller 8081:80

正常情況下會顯示:

Forwarding from 127.0.0.1:8081 -> 80

保持這個終端機執行,接著開啟另一個 PowerShell 視窗:

curl.exe -H "Host: final-demo.test" http://127.0.0.1:8081/

這裡透過 -H 指定 HTTP Host,讓 Ingress Controller 根據 final-demo.test 找到對應的 Ingress 規則。

如果成功,會回傳:
image

也可以測試先前建立的資料讀取 API:

curl.exe -H "Host: final-demo.test" http://127.0.0.1:8081/messages

如果資料仍然存在,會回傳:

[
  "Hello PVC"
]

這代表 HTTP 請求已經成功經過:

Windows
   |
   | kubectl port-forward
   v
Ingress Controller
   | 根據 Ingress Rule 選擇 Service
   v
Service
   |
   v
Pod

需要注意的是,這次使用 Port Forward 是為了在本機測試 Ingress 的路由功能,並不代表 Ingress 已經能直接從外部網路存取。

實際環境中,可以依照使用的 Kubernetes 環境,透過 NodePort、LoadBalancer 或其他網路設定提供對外存取。

總結

這篇延續 DAY27 建立的 Web API,加入資料持久化功能,並使用 Ingress 提供 HTTP 存取。

在這次實作中:

  • PVC 提供持久化儲存空間。
  • Volume 將 PVC 掛載到容器。
  • Deployment 管理應用程式的 Pod。
  • Service 提供穩定的服務入口。
  • Ingress 根據 HTTP Host 與路徑轉送請求。

透過刪除 Pod 並重新讀取資料,也驗證了持久化儲存的基本效果。

下一篇將繼續使用這個專案,加入健康檢查與資源限制,並透過故障模擬觀察 Kubernetes 的維運機制。


上一篇
# 從零開始的 Kubernetes 基礎觀念與實作 DAY27
下一篇
# 從零開始的 Kubernetes 基礎觀念與實作 DAY29
系列文
從零開始的 Kubernetes 基礎觀念與實作 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言